/*
 * Lock.cpp
 *
 *  Created on: Nov 27, 2011
 *      Author: Lucifer
 */
#include <stdlib.h>

#include "../pch.h"

#ifndef	_WIN32
#include <errno.h>
#include <poll.h>
#include <unistd.h>
#endif

namespace BFX
{

//////////////////////////////////////////////////////////////////////////////

#ifndef	_WIN32
#ifdef	__APPLE_CC__
#define	YieldProcessor()	pthread_yield_np()
#else
#define	YieldProcessor()	pthread_yield()
#endif
#endif	//	_WIN32

static bool SwitchToThread(INT32 nMilliseconds)
{
	BFX_ASSERT(nMilliseconds >= 0);

#ifdef	_WIN32
	if (nMilliseconds == 0)
		return (TRUE == ::SwitchToThread());
	else
	{
		::Sleep(nMilliseconds);
		return true;
	}

#else
	if (nMilliseconds == 0)
		return (::sched_yield() == 0);
	else
		return (::usleep(nMilliseconds * 1000) == 0);
#endif
}

enum
{
	BACKOFF_LIMIT = 1000
// used in spin to acquire
};

SpinLock::SpinLock()
{
	m_Initialized = UnInitialized;
	Init();
}

SpinLock::~SpinLock()
{
}

void SpinLock::Init()
{
	if (m_Initialized == Initialized)
	{
		// We have initialized this spinlock.
		return;
	}

	while (true)
	{
		LONG curValue = Interlocked::CompareExchange(&m_Initialized, BeingInitialized,
		        UnInitialized);
		if (curValue == Initialized)
		{
			return;
		}
		else if (curValue == UnInitialized)
		{
			// We are the first to initialize the lock
			break;
		}
		else
		{
			SwitchToThread(10);
		}
	}

	m_nLock = 0;

	BFX_ASSERT (m_Initialized == BeingInitialized);
	m_Initialized = Initialized;
}

// SpinLock::SpinToAcquire, non-inline function, called from inline Acquire
//
//  Spin waiting for a spinlock to become free.
//
//
void SpinLock::SpinToAcquire()
{
	UINT32 ulBackoffs = 0;
	UINT32 ulSpins = 0;

	while (true)
	{
		for (unsigned i = ulSpins + 10000; ulSpins < i; ulSpins++)
		{
			// Note: Must cast through volatile to ensure the lock is
			// refetched from memory.
			//
			if (*((volatile INT32*) &m_nLock) == 0)
			{
				break;
			}
			YieldProcessor();			// indicate to the processor that we are spining
		}

		// Try the inline atomic test again.
		//
		if (GetLockNoWait())
		{
			break;
		}

		//backoff
		ulBackoffs++;

		if ((ulBackoffs % BACKOFF_LIMIT) == 0)
		{
			SwitchToThread(500);
		}
		else
		{
			SwitchToThread(0);
		}
	}
}

void SpinLock::GetLock()
{
	BFX_ASSERT(m_Initialized == Initialized);

	if (!GetLockNoWait())
	{
		SpinToAcquire();
	}
	// TODO: INCTHREADLOCKCOUNT();
}

// SpinLock::GetLockNoWait
// used interlocked exchange and fast lock acquire

bool SpinLock::GetLockNoWait()
{
	if (m_nLock == 0 && Interlocked::Exchange(&m_nLock, 1) == 0)
	{
		return true;
	}
	return false;
}

// SpinLock::FreeLock
//  Release the spinlock
//
void SpinLock::FreeLock()
{
	BFX_ASSERT(m_Initialized == Initialized);

	// Uses interlocked exchange.
	//
	Interlocked::Exchange(&m_nLock, 0);
	// TODO: DECTHREADLOCKCOUNT();
}

//////////////////////////////////////////////////////////////////////////////

// Typically defined in sys/stropts.h and used for an infinite timeout.
#ifndef	_INFTIM
#define	_INFTIM	-1
#endif
#ifndef	INFTIM
#define	INFTIM	_INFTIM
#endif

//
// Initialize the critical section, Or rise ThreadStateException if failed.
//
Mutex::Mutex()
{
#ifdef	_WIN32
	::InitializeCriticalSection(&m_CritSec);
#else
	pthread_mutex_t *pMutex;

	m_nLockCount = 0;
	m_nRecursionCount = 0;
	m_nSpinCount = 0;
	m_hOwningThread = NULL;
	m_hLockSemaphore = NULL;

    // Uses a pthread_mutex_t instead of a semaphore
    pMutex = (pthread_mutex_t *) malloc(sizeof(pthread_mutex_t));
    BFX_ASSERT(pMutex != NULL);
	if (0 != pthread_mutex_init(pMutex, NULL))
	{
		free(pMutex);
		BFX_ENSURE1(false, "Unable to initialize the mutex.");
	}

	m_hLockSemaphore = (HANDLE) pMutex;
#endif
}

// Delete the critical section.
Mutex::~Mutex()
{
#ifdef	_WIN32
	::DeleteCriticalSection(&m_CritSec);
#else
	pthread_mutex_destroy((pthread_mutex_t *) m_hLockSemaphore);
	free((void *) m_hLockSemaphore);

	m_hOwningThread = NULL;
	m_nRecursionCount = 0;
	m_hLockSemaphore = NULL;
#endif
}

void Mutex::Lock()
{
#ifdef	_WIN32
	::EnterCriticalSection(&m_CritSec);
#else
	int nRetCode = 0;
	HANDLE hThread = (HANDLE) ::pthread_self();

	// check if the current thread already owns the Mutex
	if (m_hOwningThread == hThread)
	{
		m_nRecursionCount++;
	}
	else
	{
		// Critical section has been initialized properly.
		BFX_ASSERT(m_hLockSemaphore != NULL);

		nRetCode = ::pthread_mutex_lock((pthread_mutex_t*) m_hLockSemaphore);

		switch (nRetCode)
		{
		case 0:
			// mutex was acquired successfully
			BFX_ASSERT1(m_hOwningThread == NULL, "Mutex structure is corrupted.");

			m_hOwningThread = hThread;
			m_nRecursionCount = 1;
			break;
		case EINVAL:
			BFX_ENSURE1(false, "Mutex hasn't been initialized properly");
			break;

		case EDEADLK:
			// pthread_mutex_lock failed with EDEADLK.
			poll(NULL, 0, INFTIM);
			break;

		default:
			BFX_ENSURE1(false, "pthread_mutex_lock() failed with unexpected error %d (%s)");
			break;
		}
	}
#endif
}

void Mutex::Unlock()
{
#ifdef	_WIN32
	::LeaveCriticalSection(&m_CritSec);
#else
	int nRetCode;
	HANDLE hThread;

	hThread = (HANDLE) ::pthread_self();

	// first check the current thread is owning the Mutex.
	if (m_hOwningThread == hThread)
	{
		m_nRecursionCount--;
		if (m_nRecursionCount == 0)
		{
			m_hOwningThread = NULL;

			nRetCode = ::pthread_mutex_unlock((pthread_mutex_t*) m_hLockSemaphore);
			BFX_ASSERT(nRetCode == 0);
		}
	}
	else
	{
		// Current thread is not owning the Mutex.
		;
	}
#endif
}

bool Mutex::TryLock()
{
#ifdef	_WIN32
	return (TRUE == ::TryEnterCriticalSection(&m_CritSec));
#else
	bool bRetVal = false;
	int nRetCode;
	HANDLE hThread;

	hThread = (HANDLE) ::pthread_self();

	// check if the current thread already owns the Mutex
	if (m_hOwningThread == hThread)
	{
		m_nRecursionCount++;
		return true;
	}
	else
	{
		// Critical section haven't been initialized properly
		BFX_ASSERT(m_hLockSemaphore != NULL);

		// acquire the mutex
		nRetCode = ::pthread_mutex_trylock((pthread_mutex_t*) m_hLockSemaphore);

		switch (nRetCode)
		{
		case 0:
			// mutex was acquired successfully
			BFX_ASSERT1(m_hOwningThread == NULL, "Mutex structure is corrupted.");

			m_hOwningThread = hThread;
			m_nRecursionCount = 1;

			bRetVal = true;
			break;

		case EBUSY:
			// Mutex is owned by another thread
			break;

		case EINVAL:
			BFX_ENSURE1(false, "Critical section haven't been initialized properly");
			break;

		default:
			BFX_ENSURE1(false, "pthread_mutex_trylock() failed with unexpected error %d (%s)");
			break;
		}
	}

	return bRetVal;
#endif
}

bool Mutex::IsLocked()
{
#ifdef	_WIN32
	return (m_CritSec.OwningThread != NULL);
#else
	return m_hOwningThread != 0;
#endif
}

} /* namespace BFX */
